#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <assert.h>
#include <stdlib.h>

#include "material.h"
#include "vertex.h"
#include "triangle.h"
#include "vect.h"
#include "lighting.h"
#include "light.h"
#include <iostream>
#include <list>
#include <utility>
#include <vector>
using std::cout;
using std::endl;
using std::vector;
using std::list;
#include "obj_loader.h"
#include "font_related.h"
#include "menu/load_and_show_screen.h"
#include "menu/test_screen.h"

#include "menu.h"
const char* lod_alg_type::names[] = 
{
   "dispersed cubes",
   "random",
   "angle"
};
const char* lod_alg_type::disp_cubes_opts::edge_coll_type::names[] = 
{
   "half",
   "full"
};


#include "error.h"
const char* error::descriptions[] =
{
      "",
      "Could not open the file.", 
      "File Input/Output error.", 
      "Corrupted data structure in the file.", 
      "Could not allocate memory."
};


#define FRAME_COUNTER
#define C_frameCounterBucketSize 200

#define C_windowWidth 1000 // C like Constant
#define C_windowHeight 600

float G_fpsStringXPos = 100; // G like Global
float G_fpsStringYPos = 100;
int G_actScreen = displayed_screen::mainScreen;
int G_actMainMenuPos = 1;
int G_actGenerateLODScreenPos = generateLODScreen_positions::load_obj;


   // Globals
   // externs are in globals.h
bool G_usevbo = false;
GLuint G_vboid_v;
GLuint G_vboid_i;


int G_filled_or_wired = GL_LINE; // polygon mode
lighting G_lighting;

load_and_show_screen loadAndShowScreen(G_lighting,G_filled_or_wired);
test_screen testScreen(G_lighting,G_filled_or_wired);

void init(void) 
{
   glClearColor (0.0, 0.0, 0.0, 0.0);
   //if(G_filled_or_wired=='f')
    //   glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
   //else
    //   glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
   glPolygonMode(GL_FRONT_AND_BACK, G_filled_or_wired);
   

   //glShadeModel(G_shadeModel);
   //glEnable(GL_LIGHTING);
   //glEnable(GL_LIGHT0);
   glEnable(GL_DEPTH_TEST);

   light l;
   l.amb[0]=0.0;
   l.amb[1]=0.0;
   l.amb[2]=0.0;
   l.amb[3]=1.0;
   l.diff[0]=1.0;
   l.diff[1]=1.0;
   l.diff[2]=1.0;
   l.diff[3]=1.0;
   l.spec[0]=1.0;
   l.spec[1]=1.0;
   l.spec[2]=1.0;
   l.spec[3]=1.0;
   l.pos[0]=1.0;
   l.pos[1]=1.0;
   l.pos[2]=1.0;
   l.pos[3]=0.0;
   G_lighting.add_light(l);
   //GLfloat amb[4]={10.0,10.0,10.0,1.0};
   //G_lighting.set_global_ambient(amb);
   glEnable(GL_COLOR_MATERIAL);
   glColorMaterial(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE);
   glEnable(GL_NORMALIZE);
   G_lighting.enable();

   glGenBuffersARB(1, &G_vboid_v);
   glGenBuffersARB(1, &G_vboid_i);

   assert(sizeof(GL_UNSIGNED_INT)==sizeof(int));

   // I'm using float for specifying vertex coordinates in vertex.h
   assert(sizeof(GLfloat)==sizeof(float));
}

void deinit(void)
{
    glDeleteBuffersARB(1, &G_vboid_v);
    glDeleteBuffersARB(1, &G_vboid_i);
}

void reshape (int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h); 
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 30.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   //gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
}

void keyboard(unsigned char key, int x, int y)
{
   switch(G_actScreen)
   {
      case displayed_screen::mainScreen:
         switch(key)
         {
            case 'q':
            case 'Q':
            //case 27: // escape
            {
                deinit();
                exit(0);
               break;
            }
            case 13: // enter
               switch(G_actMainMenuPos)
               {
                  case main_menu_positions::generate:
                     G_actScreen = displayed_screen::generateLODScreen;
                     break;
                  case main_menu_positions::load_and_show:
                     G_actScreen = displayed_screen::loadAndShowScreen; 
                     break;
                  case main_menu_positions::performanceTest:
                     G_actScreen = displayed_screen::testScreen; 
                     break;
                  case main_menu_positions::quit:
                     deinit();
                     cout<<"LOD you next time..."<<endl;
                     exit(0);
               }
               break;
            default:
               main_menu_keyboard(key,x,y,G_actMainMenuPos);
         }
         break;

      case displayed_screen::loadAndShowScreen:
      {
         int r;
         r = loadAndShowScreen.keyboard_func(key,x,y);
         if(r==27)//escape
            G_actScreen = displayed_screen::mainScreen;
         break;
      }

      case displayed_screen::generateLODScreen:
      {
         int r;
         switch(key)
         {
            //case 27:
             //  G_actScreen = mainScreen;
              // break;
            default:
               r = generateLODScreen_keyboard(key,x,y,G_actGenerateLODScreenPos);
         }
         if(r>0)
         {
            if(r==27)
               G_actScreen = displayed_screen::mainScreen;
         }
         break;
      }
      case displayed_screen::testScreen:
      {
         int r;
         r = testScreen.keyboard_func(key,x,y);
         if(r==27)//escape
            G_actScreen = displayed_screen::mainScreen;
         break;
      }
         /*switch(key)
         {
            case 'q':
            case 'Q':
            case 27: // escape
            {
                deinit();
                exit(0);
            }
            case 'v':
                G_usevbo = !G_usevbo;
                if(G_usevbo)
                    cout << "Using vbo\n";
                else
                    cout << "Not using vbo\n";
                break;
            case 'w':
                G_eyerot.x-=5.0f;
                if(G_eyerot.x<0.f)
                    G_eyerot.x+=360.f;
                else if(G_eyerot.x>360.f)
                    G_eyerot.x-=360.f;
                break;
            case 's':
                G_eyerot.x+=5.0f;
                if(G_eyerot.x<0.f)
                    G_eyerot.x+=360.f;
                else if(G_eyerot.x>360.f)
                    G_eyerot.x-=360.f;
                break;
            case 'a':
                G_eyerot.y-=5.0f;
                if(G_eyerot.y<0.f)
                    G_eyerot.y+=360.f;
                else if(G_eyerot.y>360.f)
                    G_eyerot.y-=360.f;
                break;
            case 'd':
                G_eyerot.y+=5.0f;
                if(G_eyerot.y<0.f)
                    G_eyerot.y+=360.f;
                else if(G_eyerot.y>360.f)
                    G_eyerot.y-=360.f;
                break;
            case 'f':
                if(G_filled_or_wired=='f')
                {
                    G_filled_or_wired='w';
                    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                }
                else
                {
                    G_filled_or_wired='f';
                    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
                }
                break;
            case 'l':
                if(G_trcount>0)
                {
                    //cout << "before lod " << G_trcount << endl;
                    make_lod(*G_verts, G_verts_count, *G_triangles, G_trcount);
                    //cout << "after lod " << G_trcount << endl;
                }
                break;
         }*/
   }
      
   glutPostRedisplay();
}

void keyboard_special(int key, int x, int y)
{
    //int modif;
    switch(G_actScreen)
    {
       case displayed_screen::generateLODScreen:
       case displayed_screen::testScreen:
       case displayed_screen::mainScreen:
       {
          if(key == GLUT_KEY_UP)
             keyboard('w',x,y);
          else if(key == GLUT_KEY_DOWN)
             keyboard('s',x,y);

          break;
       }
          /*switch(key)
          {
              case GLUT_KEY_LEFT:
                  G_eyepos.x+=0.5f;
                  break;
              case GLUT_KEY_RIGHT:
                  G_eyepos.x-=0.5f;
                  break;
              case GLUT_KEY_UP:
                  modif = glutGetModifiers();
                  if(modif&GLUT_ACTIVE_SHIFT != 0)
                      G_eyepos.z+=0.5f;
                  else
                      G_eyepos.y-=0.5f;
                  break;
              case GLUT_KEY_DOWN:
                  modif = glutGetModifiers();
                  if(modif&GLUT_ACTIVE_SHIFT != 0)
                      G_eyepos.z-=0.5f;
                  else
                      G_eyepos.y+=0.5f;
                  break;
          }*/
    }
          
    glutPostRedisplay();
}

void mouse_proc(int button, int state, int x, int y)
{
   switch(G_actScreen)
   {
      case displayed_screen::mainScreen:
         break;
      case displayed_screen::loadAndShowScreen:
         loadAndShowScreen.mouse_func(button,state,x,y);
         break;
      case displayed_screen::generateLODScreen:
         generateLODScreen_mouse(button,state,x,y);
         break;
      case displayed_screen::testScreen:
         break;
   }

   glutPostRedisplay();
}

void motion_proc(int x, int y)
{
   switch(G_actScreen)
   {
      case displayed_screen::mainScreen:
         break;
      case displayed_screen::loadAndShowScreen:
         loadAndShowScreen.mouse_motion_func(x,y);
         break;
      case displayed_screen::generateLODScreen:
         generateLODScreen_mouse_motion(x,y);
         break;
      case displayed_screen::testScreen:
         break;
   }

   glutPostRedisplay();
}

void passive_motion_proc(int x, int y)
{
   switch(G_actScreen)
   {
      case displayed_screen::testScreen:
         testScreen.passive_motion_func(x,y);
         break;
   }

   glutPostRedisplay();
}

void idle_proc(void)
{
    glutPostRedisplay();
}

/*void load_vbo(vector<tvertex>& verts, int verts_count, vector<triangle>& triangles,
        int trcount)
{
   glBindBufferARB(GL_ARRAY_BUFFER_ARB, G_vboid_v);
   //glBufferDataARB(GL_ARRAY_BUFFER_ARB, verts_count*sizeof(tvertex), &verts[0], GL_STATIC_DRAW_ARB);
   glBufferDataARB(GL_ARRAY_BUFFER_ARB, verts_count*3*sizeof(float), NULL, GL_STATIC_DRAW_ARB);
   for(int i=0; i<verts_count; i++)
       glBufferSubDataARB(GL_ARRAY_BUFFER_ARB, i*3*sizeof(float), 3*sizeof(float), &verts[i]);
   glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

   glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, G_vboid_i);
   glBufferDataARB(GL_ELEMENT_ARRAY_BUFFER_ARB, trcount*sizeof(triangle), &triangles[0], GL_STATIC_DRAW_ARB);
   glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
}*/

void display(void)
{
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    switch(G_actScreen)
    {
        case displayed_screen::mainScreen:
            display_main_menu(C_windowWidth, C_windowHeight, G_actMainMenuPos);
            break;
        case displayed_screen::loadAndShowScreen:
            loadAndShowScreen.display_func(C_windowWidth, C_windowHeight);
            break;
        case displayed_screen::generateLODScreen:
            display_generateLODScreen(C_windowWidth, C_windowHeight, G_actGenerateLODScreenPos);
            break;
        case displayed_screen::testScreen:
        {
            testScreen.display_func(C_windowWidth, C_windowHeight);
            break;
            /*glTranslatef(0.f,0.f,-3.f);

            glTranslatef(G_eyepos.x,G_eyepos.y,G_eyepos.z);
            glRotatef(G_eyerot.x,1.0f,0.0f,0.0f);
            glRotatef(G_eyerot.y,0.0f,1.0f,0.0f);

            if(G_usevbo)
            {
                glColor3f(1.f,0.81f,0.41f);
                glBindBufferARB(GL_ARRAY_BUFFER_ARB, G_vboid_v);
                glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, G_vboid_i);
                glEnableClientState(GL_VERTEX_ARRAY);
                    //glVertexPointer(3, GL_FLOAT, 24, 0);
                    glVertexPointer(3, GL_FLOAT, 0, 0);
                    glDrawElements(GL_TRIANGLES, 3*G_trcount, GL_UNSIGNED_INT, 0);
                glDisableClientState(GL_VERTEX_ARRAY);
                glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
                glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
            }
            else
            {
                vertex* v;
                glColor3f(0.4f,0.81f,0.61f);
                glBegin(GL_TRIANGLES);
                for(int i=0; i<G_trcount; i++)
                {
                    v = &(*G_verts)[(*G_triangles)[i].verts[0]];
                    glVertex3f(v->xyz[0], v->xyz[1], v->xyz[2]);
                    v = &(*G_verts)[(*G_triangles)[i].verts[1]];
                    glVertex3f(v->xyz[0], v->xyz[1], v->xyz[2]);
                    v = &(*G_verts)[(*G_triangles)[i].verts[2]];
                    glVertex3f(v->xyz[0], v->xyz[1], v->xyz[2]);
                }
                glEnd();
            }*/
        }
    }
            
    glFlush();
    glutSwapBuffers();
}


int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize (C_windowWidth, C_windowHeight); 
    glutInitWindowPosition (100, 100);
    glutCreateWindow (argv[0]);
    init ();
    glutIdleFunc(idle_proc);
    glutDisplayFunc(display); 
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(keyboard_special);
    glutMouseFunc(mouse_proc);
    glutMotionFunc(motion_proc);
    glutPassiveMotionFunc(passive_motion_proc);
    glutMainLoop();
    return 0;
}



/*void make_lod(vector<tvertex>& verts, int& verts_count, 
        vector<triangle>& triangles, int& trcount)
{
    std::pair<int,int> rp;
    //for(int i=0; i<404; i++) {
    rp = find_edge_to_collapse(verts, verts_count, triangles, trcount);
    //rp = random_edge(*G_triangles, G_trcount);
    //cout << rp.first << rp.second<<endl<<std::flush;
    full_edge_collapse(verts, verts_count, triangles, trcount, rp.first, rp.second);
    clean_obj_data(verts, verts_count, triangles, trcount);
    //}
    //distant_edges(verts,verts_count,triangles,trcount,6);
}*/
